/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.stanfy.views.gallery;


import android.annotation.SuppressLint;
import android.content.Context;
import android.database.DataSetObserver;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.Adapter;

/**
 * An AdapterView is a view whose children are determined by an {@link Adapter}.
 *
 * <p>
 * See {@link ListView}, {@link android.widget.GridView}, {@link android.widget.Spinner} and
 *      {@link FlingStopTouchGallery} for commonly used subclasses of AdapterView.
 * @param <T> adapter type
 */
@SuppressLint("FieldGetter")
public abstract class AdapterView<T extends Adapter> extends ViewGroup {

  /** Logging tag. */
  protected static final String TAG = VIEW_LOG_TAG;

  /** Debug flag. */
  protected static final boolean DEBUG = false;

  /**
   * The item view type returned by {@link Adapter#getItemViewType(int)} when
   * the adapter does not want the item's view recycled.
   */
  public static final int ITEM_VIEW_TYPE_IGNORE = -1;

  /**
   * The item view type returned by {@link Adapter#getItemViewType(int)} when
   * the item is a header or footer.
   */
  public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2;

  /**
   * The position of the first child displayed.
   */
  @ViewDebug.ExportedProperty(category = "scrolling")
  int mFirstPosition = 0;

  /**
   * The offset in pixels from the top of the AdapterView to the top
   * of the view to select during the next layout.
   */
  int mSpecificTop;

  /**
   * Position from which to start looking for mSyncRowId.
   */
  int mSyncPosition;

  /**
   * Row id to look for when data has changed.
   */
  long mSyncRowId = INVALID_ROW_ID;

  /**
   * Height of the view when mSyncPosition and mSyncRowId where set.
   */
  long mSyncHeight;

  /**
   * True if we need to sync to mSyncRowId.
   */
  boolean mNeedSync = false;

  /**
   * Indicates whether to sync based on the selection or position. Possible
   * values are {@link #SYNC_SELECTED_POSITION} or
   * {@link #SYNC_FIRST_POSITION}.
   */
  int mSyncMode;

  /**
   * Our height after the last layout.
   */
  private int mLayoutHeight;

  /**
   * Sync based on the selected child.
   */
  static final int SYNC_SELECTED_POSITION = 0;

  /**
   * Sync based on the first child displayed.
   */
  static final int SYNC_FIRST_POSITION = 1;

  /**
   * Maximum amount of time to spend in {@link #findSyncPosition()}.
   */
  static final int SYNC_MAX_DURATION_MILLIS = 100;

  /**
   * Indicates that this view is currently being laid out.
   */
  boolean mInLayout = false;

  /**
   * The listener that receives notifications when an item is selected.
   */
  OnItemSelectedListener mOnItemSelectedListener;

  /**
   * The listener that receives notifications when an item is clicked.
   */
  OnItemClickListener mOnItemClickListener;

  /**
   * The listener that receives notifications when an item is long clicked.
   */
  OnItemLongClickListener mOnItemLongClickListener;

  /**
   * True if the data has changed since the last layout.
   */
  boolean mDataChanged;

  /**
   * The position within the adapter's data set of the item to select
   * during the next layout.
   */
  @ViewDebug.ExportedProperty(category = "list")
  int mNextSelectedPosition = INVALID_POSITION;

  /**
   * The item id of the item to select during the next layout.
   */
  long mNextSelectedRowId = INVALID_ROW_ID;

  /**
   * The position within the adapter's data set of the currently selected item.
   */
  @ViewDebug.ExportedProperty(category = "list")
  int mSelectedPosition = INVALID_POSITION;

  /**
   * The item id of the currently selected item.
   */
  long mSelectedRowId = INVALID_ROW_ID;

  /**
   * View to show if there are no items to show.
   */
  private View mEmptyView;

  /**
   * The number of items in the current adapter.
   */
  @ViewDebug.ExportedProperty(category = "list")
  int mItemCount;

  /**
   * The number of items in the adapter before a data changed event occured.
   */
  int mOldItemCount;

  /**
   * Represents an invalid position. All valid positions are in the range 0 to 1 less than the
   * number of items in the current adapter.
   */
  public static final int INVALID_POSITION = -1;

  /**
   * Represents an empty or invalid row id.
   */
  public static final long INVALID_ROW_ID = Long.MIN_VALUE;

  /**
   * The last selected position we used when notifying.
   */
  int mOldSelectedPosition = INVALID_POSITION;

  /**
   * The id of the last selected position we used when notifying.
   */
  long mOldSelectedRowId = INVALID_ROW_ID;

  /**
   * Indicates what focusable state is requested when calling setFocusable().
   * In addition to this, this view has other criteria for actually
   * determining the focusable state (such as whether its empty or the text
   * filter is shown).
   *
   * @see #setFocusable(boolean)
   * @see #checkFocus()
   */
  private boolean mDesiredFocusableState;
  /** mDesiredFocusableInTouchModeState. */
  private boolean mDesiredFocusableInTouchModeState;

  /** mSelectionNotifier. */
  private SelectionNotifier mSelectionNotifier;
  /**
   * When set to true, calls to requestLayout() will not propagate up the parent hierarchy.
   * This is used to layout the children during a layout pass.
   */
  boolean mBlockLayoutRequests = false;

  public AdapterView(final Context context) {
    super(context);
  }

  public AdapterView(final Context context, final AttributeSet attrs) {
    super(context, attrs);
  }

  public AdapterView(final Context context, final AttributeSet attrs, final int defStyle) {
    super(context, attrs, defStyle);
  }


  /**
   * Interface definition for a callback to be invoked when an item in this
   * AdapterView has been clicked.
   */
  public interface OnItemClickListener {

    /**
     * Callback method to be invoked when an item in this AdapterView has
     * been clicked.
     * <p>
     * Implementers can call getItemAtPosition(position) if they need
     * to access the data associated with the selected item.
     *
     * @param parent The AdapterView where the click happened.
     * @param view The view within the AdapterView that was clicked (this
     *            will be a view provided by the adapter)
     * @param position The position of the view in the adapter.
     * @param id The row id of the item that was clicked.
     */
    void onItemClick(AdapterView<?> parent, View view, int position, long id);
  }

  /**
   * Register a callback to be invoked when an item in this AdapterView has
   * been clicked.
   *
   * @param listener The callback that will be invoked.
   */
  public void setOnItemClickListener(final OnItemClickListener listener) {
    mOnItemClickListener = listener;
  }

  /**
   * @return The callback to be invoked with an item in this AdapterView has
   *         been clicked, or null id no callback has been set.
   */
  public final OnItemClickListener getOnItemClickListener() {
    return mOnItemClickListener;
  }

  /**
   * Call the OnItemClickListener, if it is defined.
   *
   * @param view The view within the AdapterView that was clicked.
   * @param position The position of the view in the adapter.
   * @param id The row id of the item that was clicked.
   * @return True if there was an assigned OnItemClickListener that was
   *         called, false otherwise is returned.
   */
  public boolean performItemClick(final View view, final int position, final long id) {
    if (mOnItemClickListener != null) {
      playSoundEffect(SoundEffectConstants.CLICK);
      mOnItemClickListener.onItemClick(this, view, position, id);
      return true;
    }

    return false;
  }

  /**
   * Interface definition for a callback to be invoked when an item in this
   * view has been clicked and held.
   */
  public interface OnItemLongClickListener {
    /**
     * Callback method to be invoked when an item in this view has been
     * clicked and held.
     *
     * Implementers can call getItemAtPosition(position) if they need to access
     * the data associated with the selected item.
     *
     * @param parent The AbsListView where the click happened
     * @param view The view within the AbsListView that was clicked
     * @param position The position of the view in the list
     * @param id The row id of the item that was clicked
     *
     * @return true if the callback consumed the long click, false otherwise
     */
    boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id);
  }


  /**
   * Register a callback to be invoked when an item in this AdapterView has
   * been clicked and held.
   *
   * @param listener The callback that will run
   */
  public void setOnItemLongClickListener(final OnItemLongClickListener listener) {
    if (!isLongClickable()) {
      setLongClickable(true);
    }
    mOnItemLongClickListener = listener;
  }

  /**
   * @return The callback to be invoked with an item in this AdapterView has
   *         been clicked and held, or null id no callback as been set.
   */
  public final OnItemLongClickListener getOnItemLongClickListener() {
    return mOnItemLongClickListener;
  }

  /**
   * Interface definition for a callback to be invoked when
   * an item in this view has been selected.
   */
  public interface OnItemSelectedListener {
    /**
     * Callback method to be invoked when an item in this view has been
     * selected.
     *
     * Impelmenters can call getItemAtPosition(position) if they need to access the
     * data associated with the selected item.
     *
     * @param parent The AdapterView where the selection happened
     * @param view The view within the AdapterView that was clicked
     * @param position The position of the view in the adapter
     * @param id The row id of the item that is selected
     */
    void onItemSelected(AdapterView<?> parent, View view, int position, long id);

    /**
     * Callback method to be invoked when the selection disappears from this
     * view. The selection can disappear for instance when touch is activated
     * or when the adapter becomes empty.
     *
     * @param parent The AdapterView that now contains no selected item.
     */
    void onNothingSelected(AdapterView<?> parent);
  }


  /**
   * Register a callback to be invoked when an item in this AdapterView has
   * been selected.
   *
   * @param listener The callback that will run
   */
  public void setOnItemSelectedListener(final OnItemSelectedListener listener) {
    mOnItemSelectedListener = listener;
  }

  public final OnItemSelectedListener getOnItemSelectedListener() {
    return mOnItemSelectedListener;
  }

  /**
   * Returns the adapter currently associated with this widget.
   *
   * @return The adapter used to provide this view's content.
   */
  public abstract T getAdapter();

  /**
   * Sets the adapter that provides the data and the views to represent the data
   * in this widget.
   *
   * @param adapter The adapter to use to create this view's content.
   */
  public abstract void setAdapter(T adapter);

  /**
   * This method is not supported and throws an UnsupportedOperationException when called.
   *
   * @param child Ignored.
   *
   * @throws UnsupportedOperationException Every time this method is invoked.
   */
  @Override
  public void addView(final View child) {
    throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
  }

  /**
   * This method is not supported and throws an UnsupportedOperationException when called.
   *
   * @param child Ignored.
   * @param index Ignored.
   *
   * @throws UnsupportedOperationException Every time this method is invoked.
   */
  @Override
  public void addView(final View child, final int index) {
    throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
  }

  /**
   * This method is not supported and throws an UnsupportedOperationException when called.
   *
   * @param child Ignored.
   * @param params Ignored.
   *
   * @throws UnsupportedOperationException Every time this method is invoked.
   */
  @Override
  public void addView(final View child, final LayoutParams params) {
    throw new UnsupportedOperationException("addView(View, LayoutParams) "
        + "is not supported in AdapterView");
  }

  /**
   * This method is not supported and throws an UnsupportedOperationException when called.
   *
   * @param child Ignored.
   * @param index Ignored.
   * @param params Ignored.
   *
   * @throws UnsupportedOperationException Every time this method is invoked.
   */
  @Override
  public void addView(final View child, final int index, final LayoutParams params) {
    throw new UnsupportedOperationException("addView(View, int, LayoutParams) "
        + "is not supported in AdapterView");
  }

  /**
   * This method is not supported and throws an UnsupportedOperationException when called.
   *
   * @param child Ignored.
   *
   * @throws UnsupportedOperationException Every time this method is invoked.
   */
  @Override
  public void removeView(final View child) {
    throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView");
  }

  /**
   * This method is not supported and throws an UnsupportedOperationException when called.
   *
   * @param index Ignored.
   *
   * @throws UnsupportedOperationException Every time this method is invoked.
   */
  @Override
  public void removeViewAt(final int index) {
    throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView");
  }

  /**
   * This method is not supported and throws an UnsupportedOperationException when called.
   *
   * @throws UnsupportedOperationException Every time this method is invoked.
   */
  @Override
  public void removeAllViews() {
    throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView");
  }

  @Override
  protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) {
    mLayoutHeight = getHeight();
  }

  /**
   * Return the position of the currently selected item within the adapter's data set.
   * @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected.
   */
  @ViewDebug.CapturedViewProperty
  public int getSelectedItemPosition() {
    return mNextSelectedPosition;
  }

  /**
   * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
   * if nothing is selected.
   */
  @ViewDebug.CapturedViewProperty
  public long getSelectedItemId() {
    return mNextSelectedRowId;
  }

  /**
   * @return The view corresponding to the currently selected item, or null
   * if nothing is selected
   */
  public abstract View getSelectedView();

  /**
   * @return The data corresponding to the currently selected item, or
   * null if there is nothing selected.
   */
  public Object getSelectedItem() {
    final T adapter = getAdapter();
    final int selection = getSelectedItemPosition();
    if (adapter != null && adapter.getCount() > 0 && selection >= 0) {
      return adapter.getItem(selection);
    } else {
      return null;
    }
  }

  /**
   * @return The number of items owned by the Adapter associated with this
   *         AdapterView. (This is the number of data items, which may be
   *         larger than the number of visible view.)
   */
  @ViewDebug.CapturedViewProperty
  public int getCount() {
    return mItemCount;
  }

  /**
   * Get the position within the adapter's data set for the view, where view is a an adapter item
   * or a descendant of an adapter item.
   *
   * @param view an adapter item, or a descendant of an adapter item. This must be visible in this
   *        AdapterView at the time of the call.
   * @return the position within the adapter's data set of the view, or {@link #INVALID_POSITION}
   *         if the view does not correspond to a list item (or it is not currently visible).
   */
  public int getPositionForView(final View view) {
    View listItem = view;
    try {
      View v = (View)listItem.getParent();
      while (!equals(v)) {
        listItem = v;
        v = (View)v.getParent();
      }
    } catch (final ClassCastException e) {
      // We made it up to the window without find this list view
      return INVALID_POSITION;
    }

    // Search the children for the list item
    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
      if (getChildAt(i).equals(listItem)) {
        return mFirstPosition + i;
      }
    }

    // Child not found!
    return INVALID_POSITION;
  }

  /**
   * Returns the position within the adapter's data set for the first item
   * displayed on screen.
   *
   * @return The position within the adapter's data set
   */
  public int getFirstVisiblePosition() {
    return mFirstPosition;
  }

  /**
   * Returns the position within the adapter's data set for the last item
   * displayed on screen.
   *
   * @return The position within the adapter's data set
   */
  public int getLastVisiblePosition() {
    return mFirstPosition + getChildCount() - 1;
  }

  /**
   * Sets the currently selected item. To support accessibility subclasses that
   * override this method must invoke the overriden super method first.
   *
   * @param position Index (starting at 0) of the data item to be selected.
   */
  public abstract void setSelection(int position);

  /**
   * Sets the view to show if the adapter is empty.
   */
  public void setEmptyView(final View emptyView) {
    mEmptyView = emptyView;

    final T adapter = getAdapter();
    final boolean empty = ((adapter == null) || adapter.isEmpty());
    updateEmptyStatus(empty);
  }

  /**
   * When the current adapter is empty, the AdapterView can display a special view
   * call the empty view. The empty view is used to provide feedback to the user
   * that no data is available in this AdapterView.
   *
   * @return The view to show if the adapter is empty.
   */
  public View getEmptyView() {
    return mEmptyView;
  }

  /**
   * Indicates whether this view is in filter mode. Filter mode can for instance
   * be enabled by a user when typing on the keyboard.
   *
   * @return True if the view is in filter mode, false otherwise.
   */
  boolean isInFilterMode() {
    return false;
  }

  @Override
  public void setFocusable(final boolean focusable) {
    final T adapter = getAdapter();
    final boolean empty = adapter == null || adapter.getCount() == 0;

    mDesiredFocusableState = focusable;
    if (!focusable) {
      mDesiredFocusableInTouchModeState = false;
    }

    super.setFocusable(focusable && (!empty || isInFilterMode()));
  }

  @Override
  public void setFocusableInTouchMode(final boolean focusable) {
    final T adapter = getAdapter();
    final boolean empty = adapter == null || adapter.getCount() == 0;

    mDesiredFocusableInTouchModeState = focusable;
    if (focusable) {
      mDesiredFocusableState = true;
    }

    super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode()));
  }

  void checkFocus() {
    final T adapter = getAdapter();
    final boolean empty = adapter == null || adapter.getCount() == 0;
    final boolean focusable = !empty || isInFilterMode();
    // The order in which we set focusable in touch mode/focusable may matter
    // for the client, see View.setFocusableInTouchMode() comments for more
    // details
    super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
    super.setFocusable(focusable && mDesiredFocusableState);
    if (mEmptyView != null) {
      updateEmptyStatus((adapter == null) || adapter.isEmpty());
    }
  }

  /**
   * Update the status of the list based on the empty parameter.  If empty is true and
   * we have an empty view, display it.  In all the other cases, make sure that the listview
   * is VISIBLE and that the empty view is GONE (if it's not null).
   */
  private void updateEmptyStatus(final boolean e) {
    boolean empty = e;
    if (isInFilterMode()) {
      empty = false;
    }

    if (empty) {
      if (mEmptyView != null) {
        mEmptyView.setVisibility(View.VISIBLE);
        setVisibility(View.GONE);
      } else {
        // If the caller just removed our empty view, make sure the list view is visible
        setVisibility(View.VISIBLE);
      }

      // We are now GONE, so pending layouts will not be dispatched.
      // Force one here to make sure that the state of the list matches
      // the state of the adapter.
      if (mDataChanged) {
        this.onLayout(false, getLeft(), getTop(), getRight(), getBottom());
      }
    } else {
      if (mEmptyView != null) { mEmptyView.setVisibility(View.GONE); }
      setVisibility(View.VISIBLE);
    }
  }

  /**
   * Gets the data associated with the specified position in the list.
   *
   * @param position Which data to get
   * @return The data associated with the specified position in the list
   */
  public Object getItemAtPosition(final int position) {
    final T adapter = getAdapter();
    return (adapter == null || position < 0) ? null : adapter.getItem(position);
  }

  public long getItemIdAtPosition(final int position) {
    final T adapter = getAdapter();
    return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position);
  }

  @Override
  public void setOnClickListener(final OnClickListener l) {
    throw new RuntimeException("Don't call setOnClickListener for an AdapterView. "
        + "You probably want setOnItemClickListener instead");
  }

  /**
   * Override to prevent freezing of any views created by the adapter.
   */
  @Override
  protected void dispatchSaveInstanceState(final SparseArray<Parcelable> container) {
    dispatchFreezeSelfOnly(container);
  }

  /**
   * Override to prevent thawing of any views created by the adapter.
   */
  @Override
  protected void dispatchRestoreInstanceState(final SparseArray<Parcelable> container) {
    dispatchThawSelfOnly(container);
  }

  /** Data observer. */
  class AdapterDataSetObserver extends DataSetObserver {

    /** State. */
    private Parcelable mInstanceState = null;

    @Override
    public void onChanged() {
      mDataChanged = true;
      mOldItemCount = mItemCount;
      mItemCount = getAdapter().getCount();

      // Detect the case where a cursor that was previously invalidated has
      // been repopulated with new data.
      if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
          && mOldItemCount == 0 && mItemCount > 0) {
        AdapterView.this.onRestoreInstanceState(mInstanceState);
        mInstanceState = null;
      } else {
        rememberSyncState();
      }
      checkFocus();
      requestLayout();
    }

    @Override
    public void onInvalidated() {
      mDataChanged = true;

      if (AdapterView.this.getAdapter().hasStableIds()) {
        // Remember the current state for the case where our hosting activity is being
        // stopped and later restarted
        mInstanceState = AdapterView.this.onSaveInstanceState();
      }

      // Data is invalid so we should reset our state
      mOldItemCount = mItemCount;
      mItemCount = 0;
      mSelectedPosition = INVALID_POSITION;
      mSelectedRowId = INVALID_ROW_ID;
      mNextSelectedPosition = INVALID_POSITION;
      mNextSelectedRowId = INVALID_ROW_ID;
      mNeedSync = false;

      checkFocus();
      requestLayout();
    }

    public void clearSavedState() {
      mInstanceState = null;
    }
  }

  @Override
  protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    removeCallbacks(mSelectionNotifier);
  }

  /** SelectionNotifier. */
  private class SelectionNotifier implements Runnable {
    @Override
    public void run() {
      if (mDataChanged) {
        // Data has changed between when this SelectionNotifier
        // was posted and now. We need to wait until the AdapterView
        // has been synched to the new data.
        if (getAdapter() != null) {
          post(this);
        }
      } else {
        fireOnSelected();
      }
    }
  }

  void selectionChanged() {
    if (mOnItemSelectedListener != null) {
      if (mInLayout || mBlockLayoutRequests) {
        // If we are in a layout traversal, defer notification
        // by posting. This ensures that the view tree is
        // in a consistent state and is able to accomodate
        // new layout or invalidate requests.
        if (mSelectionNotifier == null) {
          mSelectionNotifier = new SelectionNotifier();
        }
        post(mSelectionNotifier);
      } else {
        fireOnSelected();
      }
    }

    // we fire selection events here not in View
    if (mSelectedPosition != INVALID_POSITION && isShown() && !isInTouchMode()) {
      sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
    }
  }

  private void fireOnSelected() {
    if (mOnItemSelectedListener == null) { return; }

    final int selection = this.getSelectedItemPosition();
    if (selection >= 0) {
      final View v = getSelectedView();
      mOnItemSelectedListener.onItemSelected(this, v, selection,
          getAdapter().getItemId(selection));
    } else {
      mOnItemSelectedListener.onNothingSelected(this);
    }
  }

  @Override
  public boolean dispatchPopulateAccessibilityEvent(final AccessibilityEvent event) {
    boolean populated = false;
    // This is an exceptional case which occurs when a window gets the
    // focus and sends a focus event via its focused child to announce
    // current focus/selection. AdapterView fires selection but not focus
    // events so we change the event type here.
    if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
      event.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
    }

    // we send selection events only from AdapterView to avoid
    // generation of such event for each child
    final View selectedView = getSelectedView();
    if (selectedView != null) {
      populated = selectedView.dispatchPopulateAccessibilityEvent(event);
    }

    if (!populated) {
      if (selectedView != null) {
        event.setEnabled(selectedView.isEnabled());
      }
      event.setItemCount(getCount());
      event.setCurrentItemIndex(getSelectedItemPosition());
    }

    return populated;
  }

  @Override
  protected boolean canAnimate() {
    return super.canAnimate() && mItemCount > 0;
  }

  /**
   * Perform appropriate actions on data change: setup selection position.
   */
  void handleDataChanged() {
    if (DEBUG) { Log.v(VIEW_LOG_TAG, "---- handleDataChanged ----"); }
    final int count = mItemCount;
    boolean found = false;

    if (count > 0) {
      if (DEBUG) { Log.v(VIEW_LOG_TAG, "count > 0"); }

      int newPos;

      // Find the row we are supposed to sync to
      if (mNeedSync) {
        if (DEBUG) { Log.v(VIEW_LOG_TAG, "need to sync"); }
        // Update this first, since setNextSelectedPositionInt inspects
        // it
        mNeedSync = false;

        // See if we can find a position in the new data with the same
        // id as the old selection
        newPos = findSyncPosition();
        if (DEBUG) { Log.v(VIEW_LOG_TAG, "newPos=" + newPos); }
        if (newPos >= 0) {
          // Verify that new selection is selectable
          final int selectablePos = lookForSelectablePosition(newPos, true);
          if (DEBUG) { Log.v(VIEW_LOG_TAG, "selectablePos=" + selectablePos); }
          if (selectablePos == newPos) {
            // Same row id is selected
            if (DEBUG) { Log.v(VIEW_LOG_TAG, "decide to setNextSelectedPositionInt to " + newPos); }
            setNextSelectedPositionInt(newPos);
            found = true;
          }
        }
      }
      if (DEBUG) { Log.v(VIEW_LOG_TAG, "found=" + found); }
      if (!found) {
        // Try to use the same position if we can't find matching data
        newPos = getSelectedItemPosition();

        // Pin position to the available range
        if (newPos >= count) {
          newPos = count - 1;
        }
        if (newPos < 0) {
          newPos = 0;
        }
        if (DEBUG) { Log.v(VIEW_LOG_TAG, "newPos=" + newPos); }

        // Make sure we select something selectable -- first look down
        int selectablePos = lookForSelectablePosition(newPos, true);
        if (selectablePos < 0) {
          // Looking down didn't work -- try looking up
          selectablePos = lookForSelectablePosition(newPos, false);
        }
        if (DEBUG) { Log.v(VIEW_LOG_TAG, "selectablePos=" + selectablePos); }
        if (selectablePos >= 0) {
          if (DEBUG) { Log.v(VIEW_LOG_TAG, "decide to setNextSelectedPositionInt to " + selectablePos); }
          setNextSelectedPositionInt(selectablePos);
          checkSelectionChanged();
          found = true;
        }
      }
    }
    if (DEBUG) { Log.v(VIEW_LOG_TAG, "found=" + found); }
    if (!found) {
      if (DEBUG) { Log.v(VIEW_LOG_TAG, "notfound"); }
      // Nothing is selected
      if (DEBUG) { Log.v(VIEW_LOG_TAG, "nothing selected!"); }
      mSelectedPosition = INVALID_POSITION;
      mSelectedRowId = INVALID_ROW_ID;
      mNextSelectedPosition = INVALID_POSITION;
      mNextSelectedRowId = INVALID_ROW_ID;
      mNeedSync = false;
      checkSelectionChanged();
    }
    if (DEBUG) { Log.v(VIEW_LOG_TAG, "----------------------------------"); }
  }

  void checkSelectionChanged() {
    if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
      selectionChanged();
      mOldSelectedPosition = mSelectedPosition;
      mOldSelectedRowId = mSelectedRowId;
    }
  }

  /**
   * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
   * and then alternates between moving up and moving down until 1) we find the right position, or
   * 2) we run out of time, or 3) we have looked at every position
   *
   * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't
   *         be found
   */
  int findSyncPosition() {
    final int count = mItemCount;

    if (count == 0) {
      return INVALID_POSITION;
    }

    final long idToMatch = mSyncRowId;
    int seed = mSyncPosition;

    // If there isn't a selection don't hunt for it
    if (idToMatch == INVALID_ROW_ID) {
      return INVALID_POSITION;
    }

    // Pin seed to reasonable values
    seed = Math.max(0, seed);
    seed = Math.min(count - 1, seed);

    final long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;

    long rowId;

    // first position scanned so far
    int first = seed;

    // last position scanned so far
    int last = seed;

    // True if we should move down on the next iteration
    boolean next = false;

    // True when we have looked at the first item in the data
    boolean hitFirst;

    // True when we have looked at the last item in the data
    boolean hitLast;

    // Get the item ID locally (instead of getItemIdAtPosition), so
    // we need the adapter
    final T adapter = getAdapter();
    if (adapter == null) {
      return INVALID_POSITION;
    }

    while (SystemClock.uptimeMillis() <= endTime) {
      rowId = adapter.getItemId(seed);
      if (rowId == idToMatch) {
        // Found it!
        return seed;
      }

      hitLast = last == count - 1;
      hitFirst = first == 0;

      if (hitLast && hitFirst) {
        // Looked at everything
        break;
      }

      if (hitFirst || (next && !hitLast)) {
        // Either we hit the top, or we are trying to move down
        last++;
        seed = last;
        // Try going up next time
        next = false;
      } else if (hitLast || (!next && !hitFirst)) {
        // Either we hit the bottom, or we are trying to move up
        first--;
        seed = first;
        // Try going down next time
        next = true;
      }

    }

    return INVALID_POSITION;
  }

  /**
   * Find a position that can be selected (i.e., is not a separator).
   *
   * @param position The starting position to look at.
   * @param lookDown Whether to look down for other positions.
   * @return The next selectable position starting at position and then searching either up or
   *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
   */
  int lookForSelectablePosition(final int position, final boolean lookDown) {
    return position;
  }

  /**
   * Utility to keep mSelectedPosition and mSelectedRowId in sync.
   * @param position Our current position
   */
  void setSelectedPositionInt(final int position) {
    mSelectedPosition = position;
    mSelectedRowId = getItemIdAtPosition(position);
  }

  /**
   * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync.
   * @param position Intended value for mSelectedPosition the next time we go
   * through layout
   */
  void setNextSelectedPositionInt(final int position) {
    mNextSelectedPosition = position;
    mNextSelectedRowId = getItemIdAtPosition(position);
    // If we are trying to sync to the selection, update that too
    if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
      mSyncPosition = position;
      mSyncRowId = mNextSelectedRowId;
    }
  }

  /**
   * Remember enough information to restore the screen state when the data has
   * changed.
   *
   */
  void rememberSyncState() {
    if (getChildCount() > 0) {
      mNeedSync = true;
      mSyncHeight = mLayoutHeight;
      if (mSelectedPosition >= 0) {
        // Sync the selection state
        final View v = getChildAt(mSelectedPosition - mFirstPosition);
        mSyncRowId = mNextSelectedRowId;
        mSyncPosition = mNextSelectedPosition;
        if (v != null) {
          mSpecificTop = v.getTop();
        }
        mSyncMode = SYNC_SELECTED_POSITION;
      } else {
        // Sync the based on the offset of the first view
        final View v = getChildAt(0);
        final T adapter = getAdapter();
        if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
          mSyncRowId = adapter.getItemId(mFirstPosition);
        } else {
          mSyncRowId = NO_ID;
        }
        mSyncPosition = mFirstPosition;
        if (v != null) {
          mSpecificTop = v.getTop();
        }
        mSyncMode = SYNC_FIRST_POSITION;
      }
    }
  }
}
